﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using ParserGenerator.ParserConstruction;
using ParserGenerator.SyntacticAnalysis;

namespace ParserGenerator.GrammarConstruction
{
    [Serializable]
    public sealed class GrammarProduction
    {
        public int Number { get; internal set; }
        public GrammarNonterminalSymbol Owner { get; internal set; }
        public int Length => _symbols.Count;
        public bool IsEmpty => Length == 0;
        public int? Precedence { get; internal set; }
        public SyntacticRuleDeclarator Declarator { get; internal set; }
        public ParserAutomatonItem FirstItem { get; internal set; }
        public ParserAutomatonItem ReduceItem => FirstItem.Table[FirstItem.Number + Length];
        public GrammarGoalDescriptor Goal { get; internal set; }
        public bool IsAccept => Goal != null;
        public bool IsAutoDefined => Owner.IsAutoDefined;
        public bool NoTokens => _symbols.All(x => x.IsNonterminal);

        [NonSerialized]
        private ReadOnlyCollection<GrammarSymbol> _symbolsWrapper;
        private readonly List<GrammarSymbol> _symbols;
        public ReadOnlyCollection<GrammarSymbol> Symbols
        {
            get
            {
                if (_symbolsWrapper == null)
                    _symbolsWrapper = _symbols.AsReadOnly();
                return _symbolsWrapper;
            }
        }

        internal GrammarProduction()
        {
            _symbols = new List<GrammarSymbol>();
        }

        internal void AddSymbol(GrammarSymbol symbol)
        {
            if (symbol == null)
                throw new ArgumentNullException(nameof(symbol));
            _symbols.Add(symbol);
        }

        public bool Nullable()
        {
            return Nullable(0);
        }

        public bool Nullable(int index)
        {
            for (int i = index; i < Length; i++)
                if (!Symbols[i].Nullable)
                    return false;
            return true;
        }

        public IEnumerable<GrammarTerminalSymbol> FirstSet()
        {
            return FirstSet(0);
        }

        public IEnumerable<GrammarTerminalSymbol> FirstSet(int index)
        {
            for (int i = index; i < Length; i++)
            {
                GrammarSymbol sym = Symbols[i];
                if (sym.IsTerminal)
                {
                    yield return sym.AsTerminal;
                    break;
                }
                else
                {
                    foreach (GrammarTerminalSymbol x in sym.AsNonterminal.FirstSet)
                        yield return x;
                    if (!sym.AsNonterminal.Nullable)
                        break;
                }
            }
        }

        public sealed override string ToString()
        {
            StringBuilder sb = new StringBuilder();
            sb.Append(Owner.Name);
            sb.Append(" ->");
            if (!Declarator.Pattern.IsEmpty)
                sb.AppendFormat(" {0}", Declarator.Pattern);
            return sb.ToString();
        }
    }
}
